<!-- 表单 》 多选下拉框 -->

<template>
  <el-form-item v-bind="formItem">
    <!-- 标签 -->
    <template v-if="hasLabelSlot" #label>
      <slot name="label" :label="formItem.label">{{ formItem.label }}</slot>
    </template>

    <el-select
      v-model="model"
      v-bind="commonProps"
      :placeholder="CORE.isEmpty(model) ? placeholder : ''"
      :popperClass="popperClass"
      :filterable="!noSearch"
      :clearable="!noClear"
      :collapse-tags="fold"
      multiple
      @change="emitChange"
    >
      <el-option
        v-for="(item, i) in selectData"
        :key="`${item[0]}-${i}`"
        :class="getHideClass(item)"
        :value="item[0]"
        :label="item[1]"
        :disabled="getDisabled(item)"
      >
        <div class="CoreWeb-option">
          <slot name="item" :item="item">
            <div>{{ item[1] }}</div>
            <div v-if="showRight" class="CoreWeb-option-right">{{ item[rightIndex] }}</div>
          </slot>
        </div>
      </el-option>
    </el-select>
  </el-form-item>
</template>

<script>
// ============================== 导入依赖 ============================== //

import CORE from '@/CORE'
import mixForm from '../mixForm'
import FormSelect from '../FormSelect'

const name = 'FormSelectMulti'

// ============================== 导出组件 ============================== //

export default {
  /**
   * 名称定义 (实际使用名称)
   */
  name,

  /**
   * 混入列表 (配置选项合并)
   */
  mixins: [mixForm],

  /**
   * 属性注册 (抽取以便查阅)
   */
  props: CORE.WEB[name],

  /**
   * 状态数据 (函数返回对象)
   */
  data() {
    return {
      model: [], // 内部绑定值
      EMPTY: [], // 空值常量
      isAll: false, // 是否为全选
      isHalf: false, // 是否为半选
    }
  },

  /**
   * 计算属性 (慎用箭头函数)
   */
  computed: {
    /**
     * 是否显示右侧辅助信息
     */
    showRight: FormSelect.computed.showRight,

    /**
     * 下拉框数据
     */
    selectData: FormSelect.computed.selectData,

    /**
     * 弹出层样式类
     */
    popperClass: FormSelect.computed.popperClass,

    /**
     * 校验方法
     */
    validator() {
      const { required, check, min, minErr, join, obj, bind } = this
      return (rule, value, callback) => {
        let errMsg
        const valueList = join ? (value ? value.split(',') : []) : value
        if (min && valueList.length < min) errMsg = minErr.replace('{min}', min)
        if (check && (!required || value)) errMsg = check(value, obj, bind)
        errMsg ? callback(new Error(errMsg)) : callback()
      }
    },

    /**
     * 所有 ID 列表
     */
    allIdList() {
      return this.dict.map(([id]) => id)
    },

    /**
     * 有效 ID 列表
     */
    validIdList() {
      const { dict, functionIndex } = this
      const validList = dict.filter((item) => !(CORE.isFunction(item[functionIndex]) && item[functionIndex]()))
      return validList.map(([id]) => id)
    },

    /**
     * 全选属性
     */
    allProps() {
      const { isAll, isHalf, allText } = this
      return {
        key: `${isAll}${isHalf}`,
        indeterminate: isHalf,
        label: allText,
      }
    },
  },

  /**
   * 本地方法 (轻量重要在前)
   */
  methods: {
    /**
     * 是否不等 (可覆盖)
     */
    isDiff() {
      const { obj, prop, bindList, isMulti, model, join, dict, init } = this
      const v = obj[prop]
      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 非数组
      if (!values || !CORE.isArray(values)) return true
      // 无值，但有默认值
      if (!values.length && !CORE.isEmpty(init)) return true
      // 有值，但不相等
      if (values.join() !== model.join()) return true

      // 相等，但个别选中项不存在
      const valuesDict = values.map((value) => (dict || []).find(([id]) => value === id))
      if (valuesDict.some((item) => !item)) return true
      // 相等，且 bind 不是数组
      if (!isMulti) return false
      // 相等，判断 bind 的所有属性是否都相等
      if (join) {
        return bindList.some((key, i) => {
          if (!CORE.isString(obj[key.trim()])) return true
          return obj[key.trim()] !== valuesDict.map((item) => item[i]).join()
        })
      } else {
        return bindList.some((key, i) => {
          if (!CORE.isArray(obj[key.trim()])) return true
          return obj[key.trim()].join() !== valuesDict.map((item) => item[i]).join()
        })
      }
    },

    /**
     * 设置内部绑定值 (可覆盖)
     */
    setModel() {
      const { obj, prop, join, init, EMPTY, allIdList } = this
      const v = obj[prop]
      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 无值或非数组
      if (CORE.isEmpty(values) || !CORE.isArray(values)) this.model = init || EMPTY
      // 有值
      else this.model = values.filter((value) => allIdList.includes(value))
    },

    /**
     * 是否相等 (可覆盖)
     */
    equals(value) {
      const { obj, prop, bindList, isMulti, join, dict } = this
      const v = obj[prop]
      const values = join && CORE.isString(v) ? v ? v.split(',') : [] : v

      // 非数组
      if (!values || !CORE.isArray(values)) return false
      // 有值，但不相等
      if (values.join() !== value.join()) return false
      // 相等，且 bind 不是数组
      if (!isMulti) return true

      const valuesDict = values.map(value => (dict || []).find(([id]) => value === id))
      // 相等，判断 bind 的所有属性是否都相等
      if (join) {
        return bindList.every((key, i) => {
          if (!CORE.isString(obj[key.trim()])) return false
          return obj[key.trim()] === valuesDict.map((item) => item[i]).join()
        })
      } else {
        return bindList.every((key, i) => {
          if (!CORE.isArray(obj[key.trim()])) return false
          return obj[key.trim()].join() === valuesDict.map((item) => item[i]).join()
        })
      }
    },

    /**
     * 提交改变 (可覆盖)
     */
    emitChange() {
      const { obj, prop, bindList, isMulti, join, dict } = this

      // 设置全选半选
      this.setAllHalf()

      // 获取新值
      const values = this.getValue()

      // 判断改变
      if (this.onlyShow || this.equals(values)) return

      // 更新绑定
      if (isMulti) {
        const valuesDict = values.map((value) => (dict || []).find(([id]) => value === id))
        const trans = (i) => join ? valuesDict.map((item) => item[i]).join() : valuesDict.map((item) => item[i])
        bindList.forEach((key, i) => this.$set(obj, key, trans(i)))
        this.$emit('change', valuesDict)
      } else {
        this.$set(obj, prop, join ? values.join() : values)
        this.$emit('change', values)
      }

      // 提交之后
      this.afterEmit()
    },

    /**
     * 切换全选
     */
    changeAll(isAll) {
      this.model = this.isHalf || isAll ? this.allIdList : (this.init || this.EMPTY)
      this.isHalf = false
      this.emitChange()
    },

    /**
     * 设置全选半选
     */
    setAllHalf() {
      this.isAll = this.model.length > 0
      this.isHalf = this.isAll && this.model.length < this.validIdList.length
    },

    /**
     * 获取是否禁用
     */
    getDisabled(item) {
      const { isDisabled, grayed, functionType, functionIndex, max, model } = this
      if (isDisabled || grayed.includes(item[0])) return true

      const itemFun = item[functionIndex]
      if (functionType === 'gray' && CORE.isFunction(itemFun) && itemFun()) return true

      if (max && model.length >= max) return !model.includes(item[0])
    },

    /**
     * 获取隐藏样式类
     */
    getHideClass: FormSelect.methods.getHideClass,
  },
}
</script>
